package com.igo.onepack.channel;

import brut.androlib.Androlib;
import brut.androlib.AndrolibException;
import brut.androlib.ApkDecoder;
import brut.androlib.ApkOptions;
import brut.common.BrutException;
import brut.directory.DirectoryException;
import com.igo.onepack.utils.FileUtils;
import com.igo.onepack.utils.IOUtils;
import com.igo.onepack.utils.Log;
import com.igo.onepack.utils.SignHelper;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import java.io.*;
import java.util.Iterator;

/**
 * ApkTool 方案的多渠道打包
 */
public class ApkToolMultiChannel extends MultiChannel {

    private static final String TAG = "ApkToolMultiChannel";

    private File decodeOutDir;

    @Override
    protected void doPackMultiChannel(File apkFile, ChannelConfig channelConfig) throws ChannelException {
        try {
            decodeOutDir = new File(getOutDir(),FileUtils.getFileName(apkFile));
            //clean old out dir
            FileUtils.delete(decodeOutDir);
            decodeApk(apkFile);
            writeChannel(decodeOutDir.getAbsolutePath() + File.separator + "AndroidManifest.xml", apkFile, channelConfig);
        } finally {
            FileUtils.delete(decodeOutDir);
        }
    }

    private void decodeApk(File apkFile) throws ChannelException {
        try {
            ApkDecoder apkDecoder = new ApkDecoder();
            apkDecoder.setOutDir(decodeOutDir);
            apkDecoder.setApkFile(apkFile);
            apkDecoder.setDecodeSources((short) 0);
            apkDecoder.setForceDelete(false);
            apkDecoder.decode();
            FileUtils.delete(new File(decodeOutDir,"original\\AndroidManifest.xml"));
            deleteOldRsaAndSF();
        } catch (AndrolibException | IOException | DirectoryException e) {
            Log.e(TAG,e.getMessage(),e);
            throw new ChannelException("Decode apk failed.",e);
        }
    }

    private void deleteOldRsaAndSF() {
        File originDir = new File(decodeOutDir,"original\\META-INF");
        File[] files = originDir.listFiles();
        if (files != null) {
            for (File file:files) {
                if (file.isDirectory()) {
                    continue;
                }
                String fileName = file.getAbsolutePath();
                if (fileName.endsWith(".RSA") || fileName.endsWith(".SF")) {
                    FileUtils.delete(file);
                }
            }
        }
    }

    private void writeChannel(String androidManifest, File apkFile, ChannelConfig channelConfig) throws ChannelException {
        for (String channel : channelConfig.getChannels()) {
            writeChannel(androidManifest, channelConfig.getKey(), channel);
            reBuild(apkFile,channel);
        }
    }

    public void writeChannel(String androidManifest, String key, String channel) throws ChannelException {
        try {
            Log.d(TAG,String.format("%s channel start write",channel));
            Document doc = new SAXReader().read(new File(androidManifest));
            final Element applicationElement = doc.getRootElement().element("application");
            Iterator metaDataElementIterator = applicationElement.elementIterator("meta-data");
            boolean findChannelElement = false;
            while (metaDataElementIterator.hasNext()) {
                Element element1 = (Element) metaDataElementIterator.next();
                Attribute nameAttribute = element1.attribute("name");
                if (nameAttribute.getValue().equals(key)) {
                    Attribute valueAttribute = element1.attribute("value");
                    valueAttribute.setValue(channel);
                    findChannelElement = true;
                }
            }
            if (!findChannelElement) {
                Element newElement = applicationElement.addElement("meta-data");
                newElement.addAttribute("android:name",key);
                newElement.addAttribute("android:value",channel);
            }
            FileOutputStream out =new FileOutputStream(androidManifest);
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");
            XMLWriter xmlWriter = new XMLWriter(out,format);
            try {
                xmlWriter.write(doc);
                xmlWriter.flush();
            } finally {
                xmlWriter.close();
                IOUtils.closeQuietly(out);
            }
        } catch (DocumentException | IOException e) {
            Log.e(TAG,channel + " write channel failed");
            throw new ChannelException(String.format("%s write channel failed.",channel), e);
        } finally {
            Log.d(TAG,String.format("%s channel end write",channel));
        }
    }

    private void reBuild(File originApkFile, String channel) throws ChannelException {
        Log.d(TAG,String.format("%s start pack",channel));
        try {
            ApkOptions apkOptions = new ApkOptions();
            apkOptions.copyOriginalFiles = true;
            File newApk = new File(getOutDir(), FileUtils.makeApkName(originApkFile,channel));
            (new Androlib(apkOptions)).build(decodeOutDir, newApk);
            SignHelper signHelper = new SignHelper(getSignConfig(),channel);
            signHelper.setDeleteOriginApk(true);
            File signApk = signHelper.signApk(newApk);
            Log.d(TAG,"sign apk:"+signApk);
            Log.d(TAG,String.format("%s finish pack",channel));
        } catch (BrutException e) {
            throw new ChannelException(String.format("%s apk reBuild failed",channel),e);
        }
    }
}